﻿using Lanbt.Core.Entity.Constant;
using Lanbt.Core.Entity.Dto;
using Lanbt.Core.Entity.Dto.quartz;
using Lanbt.Core.Service.Quartz.Job;
using Lanbt.Core.Utility.ExceptionExtent;
using Lanbt.Core.Utility.Extent;
using Quartz;
using Quartz.Impl.Matchers;
using Quartz.Impl.Triggers;
using static Lanbt.Core.Entity.Enums.QuartzEnums;

namespace Lanbt.Core.Service.Quartz.Impl
{
    public class QuartzService : IQuartzService
    {
        #region Field && Ctor

        private readonly ISchedulerFactory _schedulerFactory;

        public readonly IScheduler _scheduler;

        public QuartzService(ISchedulerFactory schedulerFactory)
        {
            _schedulerFactory = schedulerFactory;
            if (_schedulerFactory == null)
            {
                throw new LanbtException(ExceptionModel.QUARTZ_SCHEDULER_FACTORY_EMPTY);
            }
            _scheduler = _schedulerFactory.GetScheduler().Result;
            if (_scheduler == null)
            {
                throw new LanbtException(ExceptionModel.QUARTZ_SCHEDULER_EMPTY);
            }
        }

        #endregion

        /// <summary>
        /// 是否有重复任务
        /// </summary>
        /// <param name="oldJobKey"></param>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        public async Task<bool> ExistsJob(JobKey oldJobKey, JobKey jobKey)
        {
            List<JobKey> jobKeys = new List<JobKey>();
            var groups = await _scheduler.GetJobGroupNames();
            foreach (var item in groups.OrderBy(x => x))
            {
                jobKeys.AddRange(await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(item)));
            }
            jobKeys.Remove(oldJobKey);
            
            return jobKeys.Contains(jobKey);
        }

        /// <summary>
        /// 获取Job详情
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        /// <exception cref="LanbtException"></exception>
        public async Task<ResultOutput<InsertJobInput>> GetJobDetail(JobKey jobKey)
        {
            var res = new ResultOutput<InsertJobInput>();
            var job = await _scheduler.GetJobDetail(jobKey);
            if (job == null)
            {
                throw new LanbtException(ExceptionModel.QUARTZ_JOB_EXIST_FALSE);
            }
            var dataMap = job.JobDataMap;
            var trigger = await _scheduler.GetTrigger(new TriggerKey(jobKey.Name, jobKey.Group));
            res.Result = new InsertJobInput
            {
                Name = jobKey.Name,
                Group = jobKey.Group,
                StartTime = trigger.StartTimeUtc.ToLocalTime().DateTime.ToString("yyyy-MM-dd HH:mm:ss"),
                EndTime = dataMap.GetString(JobParamConstant.ENDTIME),
                JobType = (JobType)int.Parse(dataMap.GetString(JobParamConstant.JOBTYPE)!),
                TriggerType = (TriggerType)int.Parse(dataMap.GetString(JobParamConstant.TRIGGERTYPE)!),
                Description = job.Description,
                CronValue = (trigger as CronTriggerImpl)?.CronExpressionString,
                IntervalTime = (trigger as SimpleTriggerImpl)?.RepeatInterval.Seconds,
                IntervalCount = (trigger as SimpleTriggerImpl)?.RepeatCount,
                APIHttpMethod = dataMap.GetString(JobParamConstant.APIHTTPMETHOD).IsNotNullOrEmpty() ? (APIHttpMethod)int.Parse(dataMap.GetString(JobParamConstant.APIHTTPMETHOD)) : null,
                ApiUrl = dataMap.GetString(JobParamConstant.APIURL),
                Head = dataMap.GetString(JobParamConstant.HEAD),
                FormBody = dataMap.GetString(JobParamConstant.FORMBODY),
                MailNotice = (MailNoticeType)int.Parse(dataMap.GetString(JobParamConstant.MAILMESSAGE) ?? "0"),
                ReceiveEmails = dataMap.GetString(JobParamConstant.RECEIVEEMAILS),
                Title = dataMap.GetString(JobParamConstant.TITLE),
                Content = dataMap.GetString(JobParamConstant.CONTENT),
            };
            return res;
        }

        /// <summary>
        /// 获取全部任务
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<PagesResultOutput<QueryJobDetailListOutput>> QueryJobDetailList(QueryJobDetailListInput input)
        {
            //获取全部jobkey
            List<JobKey> jobKeys = new List<JobKey>();
            var groups = await _scheduler.GetJobGroupNames();
            if (input.Group.IsNotNullOrEmpty())
            {
                groups = groups.Where(x => x.Contains(input.Group)).ToList();
            }
            foreach (var item in groups.OrderBy(x => x))
            {
                jobKeys.AddRange(await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(item)));
            }
            if (input.Name.IsNotNullOrEmpty())
            {
                jobKeys = jobKeys.Where(q => q.Name.Contains(input.Name)).ToList();
            }
            jobKeys = jobKeys.Skip((input.PageIndex - 1) * input.PageSize).Take(input.PageSize).ToList();

            var res = new PagesResultOutput<QueryJobDetailListOutput>();
            res.Total = jobKeys.Count;
            res.Result = new List<QueryJobDetailListOutput>();
            foreach (var jobKey in jobKeys.OrderBy(x => x.Name))
            {
                var jobDetail = await _scheduler.GetJobDetail(jobKey);
                var triggerList = await _scheduler.GetTriggersOfJob(jobKey);
                var triggers = triggerList.AsEnumerable().FirstOrDefault();

                //时间间隔
                var interval = string.Empty;
                if (triggers is SimpleTriggerImpl)
                    interval = (triggers as SimpleTriggerImpl)?.RepeatInterval.ToString();
                else
                    interval = (triggers as CronTriggerImpl)?.CronExpressionString;

                var dataMap = jobDetail.JobDataMap;
                //任务类型
                var jobType = (JobType)jobDetail.JobDataMap.GetLong(JobParamConstant.JOBTYPE);
                var triggerAddress = string.Empty;
                switch (jobType)
                {
                    case JobType.HTTP:
                        triggerAddress = dataMap.GetString(JobParamConstant.APIURL);
                        break;
                    case JobType.Email:
                        break;
                }
                res.Result.Add(new QueryJobDetailListOutput
                {
                    Group = jobKey.Group,
                    Name = jobKey.Name,
                    StartTime = triggers.StartTimeUtc.LocalDateTime,
                    EndTime = dataMap.GetString(JobParamConstant.ENDTIME).IsNullOrEmpty() ? null : DateTime.Parse(dataMap.GetString(JobParamConstant.ENDTIME)!),
                    LastErrMsg = dataMap.GetString(JobParamConstant.EXCEPTION),
                    PreviousFireTime = triggers.GetPreviousFireTimeUtc()?.LocalDateTime,
                    NextExecutionTime = triggers.GetNextFireTimeUtc()?.LocalDateTime,
                    Description = jobDetail.Description,
                    TriggerState = await _scheduler.GetTriggerState(triggers.Key),
                    Interval = interval,
                    TriggerAddress = triggerAddress,
                    RequestType = dataMap.GetString(JobParamConstant.APIHTTPMETHOD),
                    RunNumber = dataMap.GetLong(JobParamConstant.RUNNUMBER),
                    JobType = jobType.Description(),
                });
            }

            return res;
        }

        /// <summary>
        /// 获取运行次数
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        /// <exception cref="LanbtException"></exception>
        public async Task<long> GetRunNumber(JobKey jobKey)
        {
            if (!await _scheduler.CheckExists(jobKey))
            {
                throw new LanbtException(ExceptionModel.QUARTZ_JOB_EXIST_FALSE);
            }

            try
            {
                var jobDetail = await _scheduler.GetJobDetail(jobKey);

                return jobDetail.JobDataMap.GetLong(JobParamConstant.RUNNUMBER);
            }
            catch
            {
                return 0;
            }
        }

        /// <summary>
        /// 获取异常日志
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        /// <exception cref="LanbtException"></exception>
        public async Task<ResultOutput<string>> GetException(JobKey jobKey)
        {
            var jobDetail = await _scheduler.GetJobDetail(jobKey);

            if (jobDetail == null)
            {
                throw new LanbtException(ExceptionModel.QUARTZ_JOB_EXIST_FALSE);
            }

            return new ResultOutput<string>
            {
                Result = jobDetail?.JobDataMap.GetString(JobParamConstant.EXCEPTION) ?? ""
            };
        }

        /// <summary>
        /// 获取日志信列表
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        /// <exception cref="LanbtException"></exception>
        public async Task<ResultOutput<string>> QueryLogList(JobKey jobKey)
        {
            var jobDetail = await _scheduler.GetJobDetail(jobKey);

            if (jobDetail == null)
            {
                throw new LanbtException(ExceptionModel.QUARTZ_JOB_EXIST_FALSE);
            }
            var logList = jobDetail.JobDataMap.GetString(JobParamConstant.LOGLIST);
            return new ResultOutput<string>
            {
                Result = logList ?? "",
            };
        }

        /// <summary>
        /// 添加定时任务
        /// </summary>
        /// <param name="input">任务信息</param>
        /// <param name="runNumber">运行次数（修改时使用）</param>
        /// <returns></returns>
        /// <exception cref="LanbtException"></exception>
        public async Task InsertJob(InsertJobInput input, long? runNumber = null)
        {
            if (await _scheduler.CheckExists(new JobKey(input.Name, input.Group)))
            {
                throw new LanbtException(ExceptionModel.QUARTZ_JOB_EXIST_TRUE);
            }
            //给Job传递参数
            JobDataMap keyValues = new JobDataMap();
            if (input.EndTime.TryParseDatetime() != DateTime.MinValue)
            {
                if (input.EndTime.ToDateTime() < input.StartTime.ToDateTime())
                {
                    throw new LanbtException(ExceptionModel.PARAMS_ERROR, "结束时间大于开始时间");
                }
                keyValues.Put(JobParamConstant.ENDTIME, DateTime.Parse(input.EndTime).ToString("yyyy-MM-dd HH:mm:ss"));
            }
            keyValues.Put(JobParamConstant.JOBTYPE, ((int)input.JobType).ToString());
            keyValues.Put(JobParamConstant.TRIGGERTYPE, ((int)input.TriggerType).ToString());
            keyValues.Put(JobParamConstant.MAILMESSAGE, ((int)input.MailNotice).ToString());
            if (runNumber.HasValue)
            {
                keyValues.Put(JobParamConstant.RUNNUMBER, runNumber.ToString());
            }
            IJobConfigurator jobConfigurator = null;
            switch (input.JobType)
            {
                case JobType.HTTP:
                    if (input.ApiUrl.IsNullOrEmpty() || input.APIHttpMethod.IsNull())
                    {
                        throw new LanbtException(ExceptionModel.PARAMS_ERROR, "接口地址或访问方式为空");
                    }
                    if (!input.ApiUrl.StartsWith("http://") && !input.ApiUrl.StartsWith("https://"))
                    {
                        throw new LanbtException(ExceptionModel.PARAMS_ERROR, "接口地址不合法");
                    }
                    jobConfigurator = JobBuilder.Create<APIJob>();
                    keyValues.Put(JobParamConstant.APIURL, input.ApiUrl);
                    keyValues.Put(JobParamConstant.APIHTTPMETHOD, ((int)input.APIHttpMethod).ToString());
                    keyValues.Put(JobParamConstant.FORMBODY, input.FormBody);
                    keyValues.Put(JobParamConstant.HEAD, input.Head);

                    break;
                case JobType.Email:
                    if(input.ReceiveEmails.IsNullOrEmpty() || input.Title.IsNullOrEmpty() || input.Content.IsNullOrEmpty())
                    {
                        throw new LanbtException(ExceptionModel.PARAMS_ERROR, "邮箱必填参数为空");
                    }
                    jobConfigurator= JobBuilder.Create<MailJob>();
                    keyValues.Put(JobParamConstant.RECEIVEEMAILS, input.ReceiveEmails);
                    keyValues.Put(JobParamConstant.TITLE, input.Title);
                    keyValues.Put(JobParamConstant.CONTENT, input.Content);
                    break;
                default:
                    throw new LanbtException(ExceptionModel.QUARTZ_JOB_TYPE_DEFAULT);
            }
            IJobDetail job = jobConfigurator
                 .SetJobData(keyValues)
                 .WithIdentity(input.Name, input.Group)
                 .WithDescription(input.Description)
                 .Build();
            if (job == null)
            {
                throw new LanbtException(ExceptionModel.QUARTZ_JOB_EMPTY);
            }
            ITrigger trigger = null;
            TriggerBuilder triggerBuilder = TriggerBuilder.Create()
                .WithIdentity(input.Name, input.Group)
                .StartAt(new DateTimeOffset(input.StartTime.ToDateTime()))
                .ForJob(input.Name, input.Group);
            switch (input.TriggerType)
            {
                //Cron触发器
                case TriggerType.Cron:
                    if (input.CronValue.IsNullOrEmpty())
                    {
                        throw new LanbtException(ExceptionModel.PARAMS_ERROR, "请输入Cron值");
                    }
                    trigger = triggerBuilder
                        .WithCronSchedule(input.CronValue, x =>
                        {
                            x.WithMisfireHandlingInstructionFireAndProceed();
                        })
                        .Build();
                    break;
                //简单触发器
                case TriggerType.Simple:
                    if (input.IntervalTime <= 0)
                    {
                        throw new LanbtException(ExceptionModel.PARAMS_ERROR, "请输入间隔时间");
                    }
                    trigger = triggerBuilder
                        .WithSimpleSchedule(simple =>
                        {
                            simple.WithIntervalInSeconds(input.IntervalTime.Value);
                            //执行次数
                            if (input.IntervalCount.HasValue && input.IntervalCount > 0)
                            {
                                simple.WithRepeatCount(input.IntervalCount.Value);
                            }
                            else
                            {
                                simple.RepeatForever();
                            }
                            simple.WithMisfireHandlingInstructionFireNow();
                        })
                        .Build();
                    break;
                default:
                    throw new LanbtException(ExceptionModel.QUARTZ_TRIGGER_TYPE_DEFAULT);
            }
            if (trigger == null)
            {
                throw new LanbtException(ExceptionModel.QUARTZ_TRIGGER_EMPTY);
            }

            await _scheduler.ScheduleJob(job, trigger);
        }

        /// <summary>
        /// 暂停任务
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        /// <exception cref="LanbtException"></exception>
        public async Task PauseJob(JobKey jobKey)
        {
            if (!await _scheduler.CheckExists(jobKey))
            {
                throw new LanbtException(ExceptionModel.QUARTZ_JOB_EXIST_FALSE);
            }
            await _scheduler.PauseJob(jobKey);
            await _scheduler.PauseTrigger(new TriggerKey(jobKey.Name, jobKey.Group));
        }

        /// <summary>
        /// 恢复任务
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        /// <exception cref="LanbtException"></exception>
        public async Task ResumeJob(JobKey jobKey)
        {
            if (!await _scheduler.CheckExists(jobKey))
            {
                throw new LanbtException(ExceptionModel.QUARTZ_JOB_EXIST_FALSE);
            }
            await _scheduler.ResumeJob(jobKey);
            await _scheduler.ResumeTrigger(new TriggerKey(jobKey.Name, jobKey.Group));
        }

        /// <summary>
        /// 删除任务
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        /// <exception cref="LanbtException"></exception>
        public async Task DeleteJob(JobKey jobKey)
        {
            if (!await _scheduler.CheckExists(jobKey))
            {
                throw new LanbtException(ExceptionModel.QUARTZ_JOB_EXIST_FALSE);
            }
            await _scheduler.DeleteJob(jobKey);
        }
    }
}
